/**
 * Copyright ©2016-2020 northkingbpo Corporation, All Rights Reserved
 * http://www.northkingbpo.net/
 */
package profile.hujia.algorithm.simulate;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import profile.hujia.algorithm.common.CommonFunction;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;


/**
 * class info
 *
 * @author hujia
 * @date 2019-02-14 08:12
 */
@Slf4j
@Data
public class ConstraintCondition {
    //资源数
    private int peopleNumber;
    //任务数
    private int taskNumber;
    //任务种类个数
    private int keyNumber;
    private int peopleMaxCapacityNumber;
    //每种任务种类它的任务数多少
    private int[] numberOfTaskForEachKey;

    private int[] numberOfPeopleForEachKey;
    private int[] numberOfPeopleAbility;
    // 每种任务包含具体的任务。
    private int[][] keyOfTaskSpecific;
    // 每种任务包含具体的人。
    private int[][] keyOfPeopleSpecific;
    // 详细的每种人能力表。
    private int[][] peopleAbilitySpecific;
    // 每个任务能被哪些人做。
    private int[][] constraintConditionSpecific;
    private int[] peopleCapacity;
    private HashMap<Integer, Integer> taskDistributeRecord;
    private HashMap<Integer, Integer> taskCanNotDistribute;

    public ConstraintCondition(int peopleNumber, int taskNumber, int keyNumber, int peopleMaxCapacityNumber) {
        this.peopleNumber = peopleNumber;
        this.taskNumber = taskNumber;
        this.keyNumber = keyNumber;
        this.peopleMaxCapacityNumber = peopleMaxCapacityNumber;
        numberOfPeopleForEachKey = new int[keyNumber];
        numberOfTaskForEachKey = new int[keyNumber];
        numberOfPeopleAbility = new int[peopleNumber];
        keyOfTaskSpecific = new int[keyNumber][];
        keyOfPeopleSpecific = new int[keyNumber][];
        peopleAbilitySpecific = new int[peopleNumber][];
        constraintConditionSpecific = new int[taskNumber][];
        peopleCapacity = new int[peopleNumber];
        taskDistributeRecord = new HashMap<>();
        taskCanNotDistribute = new HashMap<>();
        for (int i = 0; i < peopleNumber; ++i) {
            peopleCapacity[i] = this.peopleMaxCapacityNumber;
        }
    }

    public void createData() {
        createTaskRandom();
        CommonFunction.showOneDimensional(numberOfTaskForEachKey, "numberOfTaskForEachKey");
        CommonFunction.showTwoDimensional(keyOfTaskSpecific, "keyOfTaskSpecific");
        createPeopleAbility();
        CommonFunction.showOneDimensional(numberOfPeopleForEachKey, "numberOfPeopleForEachKey");
        CommonFunction.showTwoDimensional(peopleAbilitySpecific, "peopleAbilitySpecific");
        CommonFunction.showOneDimensional(numberOfPeopleAbility, "numberOfPeopleAbility");
        createTaskToPeople();
        createKeyOfPeopleSpecific();
        CommonFunction.showTwoDimensional(keyOfPeopleSpecific, "keyOfPeopleSpecific");
//        CommonFunction.showTwoDimensional(constraintConditionSpecific, "constraintConditionSpecific");
    }

    private void createKeyOfPeopleSpecific() {
        int[] keyOfPeoplePos = new int[keyNumber];
        for (int i = 0; i < this.keyNumber; ++i) {
            int len = numberOfPeopleForEachKey[i];
            if (len == 0) {
                keyOfPeopleSpecific[i] = new int[1];
            } else {
                keyOfPeopleSpecific[i] = new int[len];
            }
        }
        for (int i = 0; i < peopleNumber; ++i) {
            for (int j = 0; j < numberOfPeopleAbility[i]; ++j) {
                int key = peopleAbilitySpecific[i][j];
                if (numberOfPeopleForEachKey[key] == 0) {
                    continue;
                }
                keyOfPeopleSpecific[key][keyOfPeoplePos[key]] = i + 1;
                ++keyOfPeoplePos[key];
            }
        }

    }

    private void createTaskToPeople() {
        HashMap<Integer, List<Integer>> taskPeople = new HashMap<>();
        for (int i = 0; i < peopleNumber; ++i) {
            for (int j = 0; j < numberOfPeopleAbility[i]; ++j) {
                int key = peopleAbilitySpecific[i][j];
                for (int k = 0; k < numberOfTaskForEachKey[key]; ++k) {
                    List<Integer> list = taskPeople.get(keyOfTaskSpecific[key][k]);
                    if (null == list) {
                        list = new ArrayList<>();
                    }
                    list.add(i + 1);
                    taskPeople.put(keyOfTaskSpecific[key][k], list);
                }
            }
        }
        for (int i = 0; i < taskNumber; ++i) {
            List<Integer> list = taskPeople.get(i);
            if (null == list) {
                constraintConditionSpecific[i] = new int[1];
            } else {
                constraintConditionSpecific[i] = new int[list.size() + 1];
                for (int j = 1; j < constraintConditionSpecific[i].length; ++j) {
                    constraintConditionSpecific[i][j] = list.get(j - 1);
                }
            }
        }
    }

    private void createTaskRandom() {
        createNumberOfTaskForEachKey();
        createSpecificTaskForEachKey();
    }

    private void createNumberOfTaskForEachKey() {
        Random random = new Random();
        int total = 0;
        int pos = 0;
        for (; pos < this.keyNumber && total < taskNumber; ++pos) {
            numberOfTaskForEachKey[pos] = random.nextInt(taskNumber * 2 / this.keyNumber + 1);
            total += numberOfTaskForEachKey[pos];
        }
        if (total <= taskNumber) {
            numberOfTaskForEachKey[this.keyNumber - 1] += taskNumber - total;
        } else {
            numberOfTaskForEachKey[pos - 1] -= total - taskNumber;
            for (int i = pos; i < this.keyNumber; ++i) {
                numberOfTaskForEachKey[i] = 0;
            }
        }
    }

    private void createPeopleAbility() {
        Random random = new Random();
        for (int i = 0; i < peopleNumber; ++i) {
            int capacityNumber = random.nextInt(this.peopleMaxCapacityNumber) + 1;
            numberOfPeopleAbility[i] = capacityNumber;
            peopleAbilitySpecific[i] = createUniqueNumber(capacityNumber, 0, this.keyNumber - 1);
            for (int j = 0; j < capacityNumber; ++j) {
                int key = peopleAbilitySpecific[i][j];
                //no task for this key,so people ability - 1
                if (numberOfTaskForEachKey[key] == 0) {
                    int len = numberOfPeopleAbility[i];
                    if (len > 0) {
                        peopleAbilitySpecific[i][j] = peopleAbilitySpecific[i][len - 1];
                        numberOfPeopleAbility[i]--;
                    }
                }
            }
            for (int j = 0; j < numberOfPeopleAbility[i]; ++j) {
                numberOfPeopleForEachKey[peopleAbilitySpecific[i][j]]++;
            }
        }
    }

    /**
     * value in [min,max]
     *
     * @param number count of number
     * @param min    minimum value
     * @param max    maximum value
     * @return the array of values
     */
    private int[] createUniqueNumber(int number, int min, int max) {
        if (number > max - min + 1) {
            log.error("args error");
            return null;
        }
        Random random = new Random();
        int[] result = new int[number];
        HashMap<Integer, Integer> repeatMap = new HashMap<>();
        for (int i = 0; i < number; ) {
            int tmp = random.nextInt(max - min + 1) + min;
            Integer tmpResult = repeatMap.get(tmp);
            if (null != tmpResult) {
                continue;
            }
            result[i] = tmp;
            repeatMap.put(tmp, tmp);
            ++i;
        }
        return result;
    }

    private void createSpecificTaskForEachKey() {
        int max = taskNumber;
        int[] array = new int[max];
        Random random = new Random();
        for (int i = 0; i < max; ++i) {
            array[i] = i;
        }
        for (int i = 0; i < this.keyNumber; ++i) {
            int taskCount = numberOfTaskForEachKey[i];
            if (0 == taskCount) {
                keyOfTaskSpecific[i] = new int[1];
                keyOfTaskSpecific[i][0] = -1;
            } else {
                keyOfTaskSpecific[i] = new int[taskCount];
                for (int k = 0; k < taskCount; ++k) {
                    int pos = random.nextInt(max);
                    keyOfTaskSpecific[i][k] = array[pos];
                    array[pos] = array[max - 1];
                    --max;
                }
            }
        }
    }
}
